﻿
namespace HyperF

open System
open System.Management
open System.Text.RegularExpressions

//---------------------------

module internal ObjSerializer =
  let serialize (obj:obj) =
    let formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter()
    use stream = new System.IO.MemoryStream()
    formatter.Serialize(stream, obj)
    stream.Seek(0L, System.IO.SeekOrigin.Begin) |> ignore
    let data = Array.zeroCreate<byte> (int stream.Length)
    stream.Read(data, 0, (int stream.Length)) |> ignore
    System.Convert.ToBase64String(data)

  let deserialize str =
    let data = System.Convert.FromBase64String(str)
    use stream = new System.IO.MemoryStream()
    stream.Write(data, 0, data.Length) |> ignore
    stream.Seek(0L, System.IO.SeekOrigin.Begin) |> ignore
    let formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter()
    formatter.Deserialize(stream)

//---------------------------



// http://msdn.microsoft.com/en-us/library/cc136822(VS.85).aspx
type VirtualMachine (vmobj:ManagementObject) =
  let vmobj = Utility.setManagementObjectAuth vmobj
  let timeFormat = "yyyyMMddHHmmss.ffffff-000"
  
  member x.ManagementObject_VM with get() = vmobj
  member x.Server = string x.ManagementObject_VM.["__server"]
  
  member x.Name = string x.ManagementObject_VM.["ElementName"]
  member x.Description = string x.ManagementObject_VM.["Description"]
  member x.Guid = new System.Guid(string x.ManagementObject_VM.["Name"])
  member x.State = enum<VMState> (int32 (x.ManagementObject_VM.["EnabledState"] :?> uint16))
  member x.HealthState = enum<VMHealthState> (int32 (x.ManagementObject_VM.["HealthState"] :?> uint16))
  member x.OperationalStatus = x.ManagementObject_VM.["OperationalStatus"] :?> uint16 array
  member x.InstallDate = System.DateTime.ParseExact( string x.ManagementObject_VM.["InstallDate"], timeFormat, null)
  member x.TimeOfLastConfigurationChange = System.DateTime.ParseExact( string x.ManagementObject_VM.["TimeOfLastConfigurationChange"], timeFormat, null)
  member x.TimeOfLastStateChange = System.DateTime.ParseExact( string x.ManagementObject_VM.["TimeOfLastStateChange"], timeFormat, null)
  
  member x.Serialize () = string x.ManagementObject_VM
  static member Deserialize (str:string) = new VirtualMachine(new ManagementObject(str))



type VirtualMachineSummary (vmobj:ManagementObject, summobj:ManagementBaseObject) =
  inherit VirtualMachine(vmobj)

  let memorySize =
    let server = string vmobj.["__server"]
    let name = string vmobj.["ElementName"]
    try 
      let mem = WMIInterface.get_VMMemory vmobj
      match mem.["AllocationUnits"].ToString().ToUpper() with
      | "KB" -> (mem.["VirtualQuantity"] :?> uint64) * 1024UL
      | "MB" -> (mem.["VirtualQuantity"] :?> uint64) * 1024UL * 1024UL
      | "GB" -> (mem.["VirtualQuantity"] :?> uint64) * 1024UL * 1024UL * 1024UL
      | "TB" -> (mem.["VirtualQuantity"] :?> uint64) * 1024UL * 1024UL * 1024UL * 1024UL
      | _ -> mem.["VirtualQuantity"] :?> uint64    
    with _ -> 0UL
  
  member x.ManagementObject_Summary with get() = summobj
  member x.MemorySize = memorySize
  
  member x.Heartbeat with get() = let h = summobj.["Heartbeat"] in if h = null then VMHeartbeat.Unknown else enum<VMHeartbeat> (int32 (h :?> uint16))
  member x.Notes with get() = string summobj.["Notes"]
  member x.CreationTime with get() = System.DateTime.ParseExact( string summobj.["CreationTime"], "yyyyMMddHHmmss.ffffff-000", null)
  member x.UpTime with get() = System.TimeSpan.FromMilliseconds(float (summobj.["UpTime"] :?> uint64))
  member x.CpuCount with get() = summobj.["NumberOfProcessors"] :?> uint16
  member x.CpuLoad with get() = try int( summobj.["ProcessorLoad"] :?> uint16 )
                                with _ -> -1
  member x.CpuLoadHistory with get() = summobj.["ProcessorLoadHistory"] :?> uint16 array

  member x.Serialize () =
    let summText = ObjSerializer.serialize summobj
    let res = [| string vmobj; summText; |]
    ObjSerializer.serialize res
  static member Deserialize (str:string) =
    let res = (ObjSerializer.deserialize str) :?> string array
    let o1 = new ManagementObject(res.[0])
    let o2 = (ObjSerializer.deserialize res.[1]) :?> ManagementBaseObject
    new VirtualMachineSummary(o1,o2)
(*
AsynchronousTasks = null
CreationTime = 20091013161401.737101-000
ElementName = VMNODE01
EnabledState = 2
GuestOperatingSystem = Windows Server 2008 R2 Standard
HealthState = 5
Heartbeat = 2
MemoryUsage = 1024
Name = BED7D282-1479-4316-905D-531F5CF762C6
Notes = Hi
NumberOfProcessors = 1
OperationalStatus = System.UInt16[]
ProcessorLoad = 0
ProcessorLoadHistory = System.UInt16[]
Snapshots = null
StatusDescriptions = System.String[]
ThumbnailImage = null
UpTime = 643386
--------------------------------------------
AdditionalAvailability = System.UInt16[]
Availability = 6
Caption = Key-Value Pair Exchange
CreationClassName = Msvm_KvpExchangeComponent
Description = Microsoft Key-Value Pair Exchange Service
DeviceID = Microsoft:2A34B1C2-FD73-4043-8A5B-DD2159BC743F
ElementName = Key-Value Pair Exchange
EnabledDefault = 7
EnabledState = 2
ErrorCleared = null
ErrorDescription = null
GuestExchangeItems = System.String[]
GuestIntrinsicExchangeItems = System.String[]
HealthState = 5
IdentifyingDescriptions = null
InstallDate = 20091020211543.000000-000
LastErrorCode = null
LocationIndicator = 4
MaxQuiesceTime = null
Name = Key-Value Pair Exchange
OperationalStatus = System.UInt16[]
OtherEnabledState = null
OtherIdentifyingInfo = null
PowerManagementCapabilities = null
PowerManagementSupported = null
PowerOnHours = null
RequestedState = 12
Status = null
StatusDescriptions = System.String[]
StatusInfo = null
SystemCreationClassName = Msvm_ComputerSystem
SystemName = 3EC127F3-1F13-4066-B048-13FF7A432F1F
TimeOfLastStateChange = null
TotalPowerOnHours = null
*)



type VMIntegrationServicesInfo (kvpobj:ManagementObject) =
  let kvpobj = Utility.setManagementObjectAuth kvpobj

  let kvpData =
    let sa =
      if kvpobj = null then
        failwith "Integration services not available"
      else 
        let arr1 = kvpobj.["GuestIntrinsicExchangeItems"] :?> string array
        let arr2 = kvpobj.["GuestExchangeItems"] :?> string array
        if arr1 <> null && arr2 <> null then Array.append arr1 arr2
        else if arr1 <> null then arr1
        else if arr2 <> null then arr2
        else failwith "Integration services not available"
       
    if sa.Length = 0 then failwith "Integration services not available"
    
    let dic = new System.Collections.Generic.Dictionary<string,string>()
    for s in sa do
      let xml = new System.Xml.XmlDocument()
      xml.LoadXml(s)
      let name = xml.SelectNodes("//PROPERTY[@NAME='Name']").[0].InnerText
      let data = xml.SelectNodes("//PROPERTY[@NAME='Data']").[0].InnerText
      dic.Add(name, data)
    dic    

  member x.ManagementObject = kvpobj

  member x.FullyQualifiedDomainName with get() = try kvpData.["FullyQualifiedDomainName"] with _ -> null
  member x.OSName with get() = try kvpData.["OSName"] with _ -> null
  member x.OSVersion with get() = try kvpData.["OSVersion"] with _ -> null
  member x.CSDVersion with get() = try kvpData.["CSDVersion"] with _ -> null
  member x.OSPlatformId with get() = try kvpData.["OSPlatformId"] with _ -> null
  member x.RDPAddressIPv4 with get() = try kvpData.["RDPAddressIPv4"] with _ -> null
  member x.RDPAddressIPv6 with get() = try kvpData.["RDPAddressIPv6"] with _ -> null
  member x.KvpDescriptions with get() = try kvpData.["Descriptions"] with _ -> null
  member x.ProcessorArchitecture with get() = try enum<ProcessorArchitecture> (Int32.Parse kvpData.["ProcessorArchitecture"])
                                              with _ -> ProcessorArchitecture.Processor_Architecture_Unknown





// http://msdn.microsoft.com/en-us/library/cc136884(VS.85).aspx
// http://msdn.microsoft.com/en-us/library/cc136933(VS.85).aspx
type DisplayController (display:ManagementObject, video:ManagementObject) =
  let display = Utility.setManagementObjectAuth display
  let video = Utility.setManagementObjectAuth video

  member x.ManagementObject_Disp with get() = display
  member x.ManagementObject_Video with get() = video
  
  member x.Description with get() = string video.["Description"]
  member x.VideoProcessor with get() = string video.["VideoProcessor"]
  member x.DisplayGUID with get() = new System.Guid(video.["DeviceID"].ToString().Split([|':'|],2).[1])
  
  member x.ResolutionH with get() = display.["CurrentHorizontalResolution"] :?> uint32
  member x.ResolutionV with get() = display.["CurrentVerticalResolution"] :?> uint32
  member x.RefreshRate with get() = display.["CurrentRefreshRate"] :?> uint32

  member x.Server = string display.["__server"]

  member x.Serialize () =
    let res = [| string display; string video |]
    ObjSerializer.serialize res
  static member Deserialize (str:string) =
    let res = (ObjSerializer.deserialize str) :?> string array
    let o1 = new ManagementObject(res.[0])
    let o2 = new ManagementObject(res.[1])
    new DisplayController(o1,o2)



type DiskController (diskobj:ManagementObject) =
  let diskobj = Utility.setManagementObjectAuth diskobj

  member x.ManagementObject with get() = diskobj

  member x.Name with get() = string diskobj.["ElementName"]
  member x.Description with get() = string diskobj.["Description"]
  member x.Limit with get() = diskobj.["Limit"] :?> uint64
  
  member x.Server = string diskobj.["__server"]
  
  member x.Serialize () = string diskobj
  static member Deserialize (str:string) = new DiskController(new ManagementObject(str))
(*  
Address = 0
AllocationUnits = Controllers
AutomaticAllocation = True
AutomaticDeallocation = True
Caption = IDE Controller 0
Connection = null
ConsumerVisibility = 3
Description = Settings for the Microsoft Virtual IDE Controller.
ElementName = IDE Controller 0
HostResource = null
InstanceID = Microsoft:8A075AAA-E7B8-4798-8894-8C119540BB1C\83F8638B-8DCA-4152-9EDA-2CA8B33039B4\0
Limit = 2
MappingBehavior = null
OtherResourceType = null
Parent = null
PoolID = null
Reservation = 1
ResourceSubType = Microsoft Emulated IDE Controller
ResourceType = 5
VirtualQuantity = 1
Weight = 0
*)



type DiskDrive (drive:ManagementObject) =
  let drive = Utility.setManagementObjectAuth drive
  let parent = Utility.setManagementObjectAuth( new ManagementObject( string drive.["Parent"] ) )

  member x.ManagementObject with get() = drive

  member x.Name with get() = string drive.["ElementName"]
  member x.Description with get() = string drive.["Description"]
  member x.DiskController with get() = new DiskController(parent) 
  member x.Address with get() = Int32.Parse( string drive.["Address"] )
  member x.Limit with get() = drive.["Limit"] :?> uint64

  member x.Server = string drive.["__server"]

  member x.Serialize () = string drive
  static member Deserialize (str:string) = new DiskDrive(new ManagementObject(str))
(*
Address = 0
AllocationUnits = Hard Drives
AutomaticAllocation = True
AutomaticDeallocation = True
Caption = Hard Drive
Connection = null
ConsumerVisibility = 3
Description = Settings for the Microsoft Virtual Hard Drive.
ElementName = Hard Drive
HostResource = null
InstanceID = Microsoft:A9335D8C-076E-43E8-8A19-C28C52CCA605\83F8638B-8DCA-4152-9EDA-2CA8B33039B4\0\0\D
Limit = 1
MappingBehavior = null
OtherResourceType = null
Parent = \\REALNODE02\root\virtualization:Msvm_ResourceAllocationSettingData.InstanceID="Microsoft:A9335D8C-076E-43E8-8A19-C28C52CCA605\\83F8638B-8DCA-4152-9EDA-2CA8B33039B4\\0"
PoolID = Microsoft:118C3BE5-0D31-4804-85F0-5C6074ABEA8F\Root
Reservation = 1
ResourceSubType = Microsoft Synthetic Disk Drive
ResourceType = 22
VirtualQuantity = 1
VirtualSystemIdentifiers = null
Weight = 0
*)



type VhdInfo (path:string, doc:System.Xml.XmlDocument) =
  let getProp propertyName =
    //let xpath = sprintf @"//PROPERTY[@NAME = '%s']/VALUE/child::text()" propertyName
    let xpath = sprintf @"//PROPERTY[@NAME = '%s']/VALUE" propertyName
    let node = doc.SelectSingleNode(xpath)
    //node.Value
    node.InnerText

  member x.FileSize with get() = System.UInt64.Parse( getProp "FileSize" )
  member x.MaxInternalSize with get() = System.UInt64.Parse( getProp "MaxInternalSize" )
  member x.DiskType with get() = enum<HyperF.DiskType> (System.Int32.Parse( getProp "Type" ))
  member x.InUse with get() = System.Boolean.Parse( getProp "InUse" )
  member x.InSavedState with get() = System.Boolean.Parse( getProp "InSavedState" )
  member x.ParentPath with get() = getProp "ParentPath"
  member x.Path with get() = path

  member x.Serialize () =
    let sb = new System.Text.StringBuilder()
    use xmlwr = System.Xml.XmlWriter.Create(sb)
    doc.WriteContentTo(xmlwr)
    xmlwr.Close()
    let res = [| path; string sb |]
    ObjSerializer.serialize res
  static member Deserialize (str:string) =
    let res = (ObjSerializer.deserialize str) :?> string array
    let xml = new System.Xml.XmlDocument()
    xml.LoadXml(res.[1])
    new VhdInfo( res.[0], xml )



type Disk (diskobj:ManagementObject) =
  let diskobj = Utility.setManagementObjectAuth diskobj
  let parent = Utility.setManagementObjectAuth( new ManagementObject( string diskobj.["Parent"] ) )
  let parentparent = Utility.setManagementObjectAuth( new ManagementObject( string parent.["Parent"] ) )
  let server = string diskobj.["__server"]

  member x.ManagementObject with get() = diskobj
  member x.DiskDrive with get() = new DiskDrive(parent)
  member x.DiskController with get() = new DiskController(parentparent)

  member x.Name with get() = string diskobj.["ElementName"]
  member x.Description with get() = string diskobj.["Description"]
  member x.Connection with get() = diskobj.["Connection"] :?> string array
  
  member x.GetVhdInfo () =
    [| for c in x.Connection do
         let (server,path) =
           if c.StartsWith(@"\\") then WMIInterface.resolveUncPath c
           else (server,c)
         let xml = WMIInterface.get_VHDInfo (Some server) path
         yield new VhdInfo(path, xml) |]

  member x.Serialize () = string diskobj
  static member Deserialize (str:string) = new Disk(new ManagementObject(str))
(*
Address = null
AllocationUnits = Disks
AutomaticAllocation = True
AutomaticDeallocation = True
Caption = Hard Disk Image
Connection = System.String[]
ConsumerVisibility = 3
Description = Settings for the Microsoft Hard Disk Image.
ElementName = Hard Disk Image
HostResource = null
InstanceID = Microsoft:BDCA8BF0-8163-45E9-8CE8-439E5A1F69F3\83F8638B-8DCA-4152-9EDA-2CA8B33039B4\0\0\L
Limit = 1
MappingBehavior = null
OtherResourceType = null
Parent = \\REALNODE02\root\virtualization:Msvm_ResourceAllocationSettingData.InstanceID="Microsoft:BDCA8BF0-8163-45E9-8CE8-439E5A1F69F3\\83F8638B-8DCA-4152-9EDA-2CA8B33039B4\\0\\0\\D"
PoolID = Microsoft:70BB60D2-A9D3-46aa-B654-3DE53004B4F8\Root
Reservation = 1
ResourceSubType = Microsoft Virtual Hard Disk
ResourceType = 21
VirtualQuantity = 1
VirtualSystemIdentifiers = null
Weight = 0
*)


type NetworkSwitchType =
| Private
| Internal
| External

type NetworkSwitch (switchobj:ManagementObject, swType:NetworkSwitchType)  =
  let switchobj = Utility.setManagementObjectAuth switchobj

  member x.ManagementObject with get() = switchobj
  
  member x.SwitchType with get() = swType
  member x.Name with get() = string switchobj.["ElementName"]
  member x.Description with get() = string switchobj.["Description"]
  member x.Guid
    with get() =
      let name = string switchobj.["Name"]
      let regEx = new Regex("(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})", RegexOptions.IgnoreCase ||| RegexOptions.Compiled)
      let m = regEx.Match(name)
      if (m.Success) then new Guid(m.Value)
      else failwith "No GUID found for this device"

  member x.Server = string switchobj.["__server"]

  member x.Serialize () = 
    match x.SwitchType with
    | NetworkSwitchType.Private -> "0" + string switchobj
    | NetworkSwitchType.Internal -> "1" + string switchobj
    | NetworkSwitchType.External -> "2" + string switchobj
  static member Deserialize (str:string) =
    match str.[0] with
    | '0' -> new NetworkSwitch(new ManagementObject(str.Substring(1)), NetworkSwitchType.Private)
    | '1' -> new NetworkSwitch(new ManagementObject(str.Substring(1)), NetworkSwitchType.Internal)
    | '2' -> new NetworkSwitch(new ManagementObject(str.Substring(1)), NetworkSwitchType.External)
    | _ -> failwith "Invalid serialized string"
(*
Caption = Virtual Switch
CreationClassName = Msvm_VirtualSwitch
Dedicated = null
Description = Microsoft Virtual Switch
ElementName = Private
EnabledDefault = 2
EnabledState = 5
HealthState = 5
IdentifyingDescriptions = null
InstallDate = null
MaxChimneyOffloads = 0
MaxVMQOffloads = 0
Name = Switch-SM-a605cc87-36b1-459d-99fd-f98982e99c63-0   || Name = 21b0c03b-446f-44db-a5d1-8121ee8672b5
NameFormat = null
NumLearnableAddresses = 100
OperationalStatus = System.UInt16[]
OtherDedicatedDescriptions = null
OtherEnabledState = null
OtherIdentifyingInfo = null
PowerManagementCapabilities = null
PrimaryOwnerContact = null
PrimaryOwnerName = NT AUTHORITY\SYSTEM
RequestedState = 12
ResetCapability = 5
Roles = null
ScopeOfResidence = 
Status = null
StatusDescriptions = System.String[]
TimeOfLastStateChange = null
*)


// http://msdn.microsoft.com/en-us/library/cc136902(VS.85).aspx
type NetworkSwitchPort (portobj:ManagementObject) =
  let portobj = Utility.setManagementObjectAuth portobj

  member x.ManagementObject with get() = portobj
  
  member x.Name with get() = string portobj.["ElementName"]
  member x.Description with get() = string portobj.["Description"]
  member x.Guid with get() = new System.Guid( string portobj.["Name"] )

  member x.Server = string portobj.["__server"]

  member x.Serialize () = string portobj
  static member Deserialize (str:string) = new NetworkSwitchPort(new ManagementObject(str))



type NetworkIC (nicobj:ManagementObject) =
  let nicobj = Utility.setManagementObjectAuth nicobj

  member x.ManagementObject with get() = nicobj
  member x.Server = string nicobj.["__server"]
  
  member x.Name with get() = string nicobj.["ElementName"]
  member x.Description with get() = string nicobj.["Description"]
  member x.MacAddress with get() = string nicobj.["Address"]
  member x.IsStaticMacAddress with get() = nicobj.["StaticMacAddress"] :?> bool
  
  member x.Connection
      with get() =
        let conns = nicobj.["Connection"] :?> string array
        conns |> Array.filter (fun c -> c <> null && c <> "") 
              |> Array.map (fun c -> new NetworkSwitchPort(new ManagementObject(c)))

  member x.Serialize () = string nicobj
  static member Deserialize (str:string) = new NetworkIC(new ManagementObject(str))
(*
Address = 00155D000203
AllocationUnits = Ports
AutomaticAllocation = True
AutomaticDeallocation = True
Caption = Ethernet Port
Connection = System.String[]
ConsumerVisibility = 3
Description = Settings for the Microsoft Emulated Ethernet Port.
ElementName = Legacy Network Adapter
HostResource = null
InstanceID = Microsoft:3EC127F3-1F13-4066-B048-13FF7A432F1F\718032DA-6EFB-4EF4-8152-9A2FBDA945BF\0
Limit = 1
MappingBehavior = null
OtherResourceType = null
Parent = null
PoolID = Microsoft:4EA4F71F-16E6-4250-99A8-A2315332CC64\Root
Reservation = 1
ResourceSubType = Microsoft Emulated Ethernet Port
ResourceType = 10
StaticMacAddress = False
VirtualQuantity = 1
Weight = 0
*)

type ExternalEthernetDevice (extEthObj:ManagementObject) =
  let extEthObj = Utility.setManagementObjectAuth extEthObj

  member x.ManagementObject with get() = extEthObj
  member x.Server = string extEthObj.["__server"]
  
  member x.Name with get() = string extEthObj.["ElementName"]
  member x.Guid with get() = new System.Guid(string extEthObj.["DeviceID"])
  member x.Description with get() = string extEthObj.["Description"]
  member x.MacAddress with get() = string extEthObj.["PermanentAddress"]
  member x.SystemName with get() = string extEthObj.["SystemName"]
  member x.Mtu with get() = extEthObj.["SupportedMaximumTransmissionUnit"] :?> uint64
  
  member x.Serialize () = string extEthObj
  static member Deserialize (str:string) = new ExternalEthernetDevice(new ManagementObject(str))
(*
ActiveMaximumTransmissionUnit = 1500
AdditionalAvailability = null
AutoSense = True
Availability = null
Capabilities = null
CapabilityDescriptions = null
Caption = Ethernet Port
CreationClassName = Msvm_ExternalEthernetPort
Description = Microsoft External Ethernet Port
DeviceID = {4EA59E81-7C1D-4DB2-A435-7130D28CB01B}
ElementName = Broadcom BCM5706S NetXtreme II GigE (NDIS VBD Client)
EnabledCapabilities = null
EnabledDefault = 2
EnabledState = 2
ErrorCleared = null
ErrorDescription = null
FullDuplex = True
HealthState = 5
IdentifyingDescriptions = null
InstallDate = null
IsBound = True
LastErrorCode = null
LinkTechnology = 2
LocationIndicator = null
MaxDataSize = 1500
MaxQuiesceTime = null
MaxSpeed = 1000000000
Name = Broadcom BCM5706S NetXtreme II GigE (NDIS VBD Client)
NetworkAddresses = null
OperationalStatus = System.UInt16[]
OtherEnabledCapabilities = null
OtherEnabledState = null
OtherIdentifyingInfo = null
OtherLinkTechnology = null
OtherNetworkPortType = null
OtherPortType = null
PermanentAddress = 001B78716D66
PortNumber = 0
PortType = 53
PowerManagementCapabilities = null
PowerManagementSupported = null
PowerOnHours = null
RequestedSpeed = null
RequestedState = 12
Speed = 1000000000
Status = OK
StatusDescriptions = System.String[]
StatusInfo = null
SupportedMaximumTransmissionUnit = 1500
SystemCreationClassName = Msvm_ComputerSystem
SystemName = NODE01
TimeOfLastStateChange = null
TotalPowerOnHours = null
UsageRestriction = 4
*)